
// Copyright (c) WanSheng Intelligent Corp. All rights reserved.
// Licensed under the LGPL v3.0 license. 


#include "modbus_plugin.h"
#include "cJSON.h"

#include <byteswap.h>

// return the data length in the buffer 
int convert_coil_from_payload(char * buffer, char * payload)
{

    if(strcmp(payload, "true") == 0)
        * buffer = 1;
    else if(strcmp(payload, "false") == 0)
        * buffer = 1;
    else if(strcmp(payload, "0") == 0)
        * buffer = 0;
    else if(strcmp(payload, "1") == 0)
        * buffer = 1;
    else
    {
        WARNING("coil payload cann't recognize. <%s>", payload);
        return 0;
    }

    return 1;
}

bool is_coil_fc(int fc)
{
    if (fc == MODBUS_FC_READ_COILS ||
        fc == MODBUS_FC_WRITE_SINGLE_COIL ||
        fc == MODBUS_FC_WRITE_MULTIPLE_COILS ||
        fc == MODBUS_FC_READ_DISCRETE_INPUTS)
        return true;
    else
        return false;
}

bool do_coding(mb_coding_e coding, uint32_t * from, uint32_t * to)
{
    if((coding == Coding_F_abcd) || (coding == Coding_I_abcd))
        my_modbus_set_abcd(from, (uint16_t *)to);
    else if((coding == Coding_F_dcba) || (coding == Coding_I_dcba))
        my_modbus_set_dcba(from, (uint16_t *)to);
    else if((coding == Coding_F_badc) || (coding == Coding_I_badc))
        my_modbus_set_badc(from, (uint16_t *)to);
    else if((coding == Coding_F_cdab) || (coding == Coding_I_cdab))
        my_modbus_set_cdab(from, (uint16_t *)to);
    else {
        return (false);
    }    

    return true;
}

// return the data length in the buffer 
int convert_register_from_payload(
        char * buffer, double write_value,
        mb_coding_e coding,
        double * multiplier)
{
    const char* sz_coding = modbus_coding_text(coding);
    if(multiplier && *multiplier >0)
        write_value /= (*multiplier);    
    // 
    if(!MB_CODING_IS_VALID(coding))
    {
        uint16_t * value = (uint16_t*)buffer;
        *value = (uint16_t) write_value;
        return sizeof(uint16_t);    
    }

    if(MB_CODING_IS_FLOAT(coding))
    {
        float v = (float)(write_value + 0.00001);

        unsigned char * c = (unsigned char *) &v;
        TraceV(FLAG_VALUE_CONVERT,"convert_register_from_payload: float ORG: [%02X %02X %02X %02X] --> %f",
            c[0], c[1], c[2],c[3], v );

        uint32_t * pInt32 = (uint32_t *) &v;
        uint32_t n = htonl(* pInt32);

        c = (unsigned char *) &n;
        TraceV(FLAG_VALUE_CONVERT,"convert_register_from_payload: net order: [%02X %02X %02X %02X] --> %u",
            c[0], c[1], c[2],c[3], n );

        if(!do_coding(coding, (uint32_t *) &v, (uint32_t *)buffer))
        {
            LOG_RETURN (0);
        }
        c = (unsigned char *) buffer;
        TraceV(FLAG_VALUE_CONVERT,"convert_register_from_payload: AFTER [%s]: [%02X %02X %02X %02X] --> %u", sz_coding,
            c[0], c[1], c[2],c[3], *((uint32_t *)buffer));

        return sizeof(float);
    } 

   
    if(MB_CODING_IS_INT(coding))
    {
        uint32_t v = (uint32_t) write_value;
        unsigned char * c = (unsigned char *) &v;
        TraceV(FLAG_VALUE_CONVERT,"convert_register_from_payload: integer ORG: [%02X %02X %02X %02X] --> %u",
            c[0], c[1], c[2],c[3], v );

        uint32_t n = htonl(v);
        c = (unsigned char *) &n;
        TraceV(FLAG_VALUE_CONVERT,"convert_register_from_payload: net order: [%02X %02X %02X %02X] --> %u",
            c[0], c[1], c[2],c[3], n );

        if(!do_coding(coding, (uint32_t *) &v, (uint32_t *)buffer))
        {
            LOG_RETURN (0);
        }
        c = (unsigned char *) buffer;
        TraceV(FLAG_VALUE_CONVERT,"convert_register_from_payload: AFTER [%s]: [%02X %02X %02X %02X] --> %u", sz_coding,
            c[0], c[1], c[2],c[3], *((uint32_t *)buffer));

        return sizeof(uint32_t);
    }

    return 0;
}

int write_command_to_modbus(i_modbus_command_t * cmd, int fmt)
{
    i_modbus_t * bus = cmd->bus;
    int nb = 1;
    int rc = -1;
    char * write_buf_alloc = NULL;
    mb_coding_e coding = cmd->coding;
    cJSON *root_array = NULL;

    bus->current_slave_id =  cmd->slave_id;
    if (IA_TEXT_PLAIN == cmd->fmt)
    {
        char write_buf[10] ={0};
        if (is_coil_fc(cmd->fc))
        {
            nb = convert_coil_from_payload((char*)write_buf, (char*)cmd->data);
        }
        else
        {
            double write_value = atof(cmd->data);
            nb = convert_register_from_payload(write_buf, write_value, coding, NULL);
            if(nb>1 && cmd->fc == MODBUS_FC_WRITE_SINGLE_REGISTER) 
            {
                cmd->fc = MODBUS_FC_WRITE_MULTIPLE_REGISTERS;
            }
        }
        if (0 == nb)
        {
            LOG_MSG("write_command_to_modbus: failed to find the register");
            goto end;
        }

        rc = exec_modbus_command(bus, cmd->fc, cmd->addr, nb, write_buf, NULL);
        if (rc == -1)
        {
            goto end;
        }
    }
    else if (IA_APPLICATION_JSON == cmd->fmt)
    {
        cJSON *root_array = cJSON_Parse_Data (cmd->data, cmd->data_len);
        if(root_array == NULL) goto end;

        if(!cJSON_IsArray(root_array)) 
        {
            goto end;
        }

        int write_cnt = (int)cJSON_GetArraySize(root_array);
        int elem_bytes = 2;
        if (is_coil_fc(cmd->fc)) 
            elem_bytes = 1;
        else if(MB_CODING_IS_VALID(coding)) 
        {
            elem_bytes = 4;
            cmd->fc = MODBUS_FC_WRITE_MULTIPLE_REGISTERS;  // must use it to write 4 bytes values
        }
        write_buf_alloc = (char*)malloc_z(write_cnt * elem_bytes);
        if(write_buf_alloc == NULL)
        {
            LOG_GOTO("Malloc fail", end);
        }

        // 
        for(int i = 0; i < write_cnt; i++)
        {
            cJSON * item = cJSON_GetArrayItem(root_array, i);
            if(!cJSON_IsNumber(item)) 
            {
                goto end;
            }
            double value = cJSON_GetNumberValue(item);
            if (is_coil_fc(cmd->fc))
            {
                write_buf_alloc[i] = (uint8_t) (value);
            }
            else if(!MB_CODING_IS_VALID(coding))
            {
                *((uint16_t * ) write_buf_alloc +i) = (uint16_t) value;
            }
            else if (MB_CODING_IS_INT(coding))
            {
                uint32_t v = (uint32_t) value;
                if(!do_coding(coding, (uint32_t *) &v, ((uint32_t * ) write_buf_alloc)+ i))
                {
                    LOG_GOTO ("coding fail", end);
                }
            }
            else  if (MB_CODING_IS_FLOAT(coding))
            {
                float v = (float) value;
                if(!do_coding(coding, (uint32_t *) &v, ((uint32_t * ) write_buf_alloc)+ i))
                {
                    LOG_GOTO ("coding fail", end);
                }
            }
        }

        if(MODBUS_FC_WRITE_MULTIPLE_COILS == cmd->fc || MODBUS_FC_WRITE_MULTIPLE_REGISTERS == cmd->fc)
        {
            int regs = write_cnt * (cmd->coding?2:1);
            rc = exec_modbus_command(bus, cmd->fc, cmd->addr, 
                regs, write_buf_alloc, NULL);
            if (rc == -1)
            {
                WARNING2("write multi fail (%d). fc=%d, addr=%d, regs=%d",
                    rc, cmd->fc, cmd->addr, regs);
                goto end;
            }
        }
        else
        {
            int item_bytes = 2;
            if(is_coil_fc(cmd->fc)) item_bytes = 1;
            for(int i = 0; i < write_cnt; i++)
            {
                rc = exec_modbus_command(bus, cmd->fc, (cmd->addr + i), 
                    1, write_buf_alloc + item_bytes*i, NULL);
                if (rc == -1)
                {
                    WARNING2("write fail (%d). fc=%d, addr=%d",
                        rc, cmd->fc, cmd->addr+i);
                    goto end;
                }
            }
        }
    }

end:
    if(root_array) cJSON_Delete(root_array);
    if(write_buf_alloc) free(write_buf_alloc);
    return rc;
}
